package com.example.xianji.utils.exit_app;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;

/**
 * 作者：张玉辉 on 2018/4/23 09:55.
 */
public class WeakArrayList<T> extends AbstractList<T> {
    private static final Object NULL_VALUE = new Object();
    private final ReferenceQueue<T> queue = new ReferenceQueue<T>();
    private Object[] data;
    private int size;
    private boolean enquedElement;
    private ReferenceListener mReferenceListener;

    @SuppressWarnings("unchecked")
    private static <T> T maskNull(T value){
        return (T) (value == null ? WeakArrayList.NULL_VALUE : value);
    }

    private static <T> T unmaskNull(T value) {
        return (value == WeakArrayList.NULL_VALUE ? null : value);
    }

    public WeakArrayList(int initialCapacity) {
        this.enquedElement = false;
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
        }
        this.data = new Object[initialCapacity];
        this.size = 0;
    }

    public WeakArrayList(Collection<? extends T> c) {
        this.enquedElement = false;
        this.data = new Object[c.size()];
        this.size = this.data.length;
        int i = 0;
        for (T t : c) {
            this.data[i] = this.createRef(t);
            ++i;
        }
    }

    private Reference<T> createRef(T obj) {
        return new WeakReference<T>(WeakArrayList.maskNull(obj), this.queue);
    }

    @SuppressWarnings("unchecked")
    public String toString() {
        StringBuffer buffer = new StringBuffer();

        for (int i = 0; i < this.size; ++i) {
            Object obj;
            Reference<T> ref = (Reference<T>) this.data[i];
            if (this.data[i] == null) {
                obj = null;
            } else {
                obj = ref.get();
            }
            buffer.append('{');
            buffer.append(obj == null ? null : obj.toString());
            buffer.append('}');
        }
        return buffer.toString();
    }

    public void ensureCapacity(int minCapacity) {
        this.modCount += 1;
        int oldCapacity = this.data.length;
        if (minCapacity > oldCapacity) {
            Object[] oldData = this.data;
            int newCapacity = oldCapacity * 3 / 2 + 1;
            if (newCapacity < minCapacity) {
                newCapacity = minCapacity;
            }
            this.data = Arrays.copyOf(oldData, newCapacity);
        }
    }

    public void trimToSize() {
        this.modCount += 1;
        int oldCapacity = this.data.length;
        if (this.size < oldCapacity) {
            this.data = Arrays.copyOf(this.data, this.size);
        }
    }

    @SuppressWarnings("unchecked")
    public int expurge() {
        int j;
        while (this.queue.poll() != null) {
            this.enquedElement = true;
        }
        if (this.enquedElement) {
            j = 0;
            for (int i = 0; i < this.size; ++i) {
                Reference<T> ref = (Reference<T>) this.data[i];
                if (ref == null || ref.isEnqueued() || ref.get() == null) {
                    if (ref != null) {
                        ref.clear();
                    }
                    this.data[i] = null;
                } else {
                    if (i != j) {
                        this.data[j] = this.data[i];
                        this.data[i] = null;
                    }
                    ++j;
                }
            }
            this.enquedElement = false;
        } else {
            j = this.size;
        }
        while (this.queue.poll() != null) {
            this.enquedElement = true;
        }
        if(this.size > j && mReferenceListener != null){
            mReferenceListener.onReferenceReleased(this.size - j);
        }
        this.size = j;
        return this.size;
    }

    protected void assertRange(int index, boolean allowLast) {
        int csize = this.expurge();
        if (index < 0) {
            throw new IndexOutOfBoundsException("invalid negative value: " + Integer.toString(index));
        }
        if (allowLast && index > csize) {
            throw new IndexOutOfBoundsException("index>" + csize + ": " + Integer.toString(index));
        }
        if (!allowLast && index >= csize) {
            throw new IndexOutOfBoundsException("index>=" + csize + ": " + Integer.toString(index));
        }
    }

    public WeakArrayList() {
        this(10);
    }

    @SuppressWarnings("unchecked")
    @Override
    public T get(int index) {
        Object value;
        do {
            this.assertRange(index, false);
            value = ((Reference<T>) this.data[index]).get();
        } while (value == null);
        return (T) WeakArrayList.unmaskNull(value);
    }

    @Override
    public int size() {
        return this.expurge();
    }

    @SuppressWarnings("unchecked")
    @Override
    public T set(int index, T element) {
        return super.set(index, element);
    }

    @Override
    public void add(int index,T element) {
        this.assertRange(index, true);
        this.ensureCapacity(this.size + 1);
        System.arraycopy(this.data, index, this.data, index + 1, this.size - index);
        this.data[index] = this.createRef(element);
        this.size += 1;
        this.modCount += 1;
    }

    @SuppressWarnings("unchecked")
    @Override
    public T remove(int index) {
        Object oldValue;
        Reference<T> ref;
        do {
            this.assertRange(index, false);
            ref = (Reference<T>) this.data[index];
            oldValue = ref.get();
        } while (oldValue == null);
        ref.clear();
        System.arraycopy(this.data, index + 1, this.data, index, this.size - index - 1);
        this.data[(this.size - 1)] = null;
        this.size -= 1;
        this.modCount += 1;
        return (T) WeakArrayList.unmaskNull(oldValue);
    }

    public void setReferenceListener(ReferenceListener l){
        this.mReferenceListener = l;
    }
}
